"use strict"
// 2D界面/绘图库 by clonne/2021

import "./ramda.js"
import "./jquery.js"

class FrameSystem {
    constructor() {
        this._frame_fns = []
        this._event_quene = []
    }

    frame_join(fn) {
        this._frame_fns.push(fn)
    }

    event_push(name, val) {
        this._event_quene.push([name,val])
    }

    event_poll(name) {
        const i = R.findIndex((a)=>(a[0] == name), this._event_quene)
        const _event_quene = this._event_quene
        return {
            on(fn){
                if (i >= 0) {
                    const val = _event_quene[i][1]
                    const fn_take = ()=>{
                        if (_event_quene.length > 1) {
                            _event_quene[i] = _event_quene.at(-1)
                        }
                        if (_event_quene.length > 0) {
                            _event_quene.pop()
                        }
                    }
                    fn(val, fn_take)
                }
            }
        }
    }

    on_frame() {
        R.forEach(R.applyTo(undefined), this._frame_fns)
    }
}

const Tools = {
    update_css(dom, key, target){
        if (dom.css(key) != target){dom.css(key, target)}
        return this
    },
    update_attr(dom, key, target){
        if (dom.attr(key) != target){dom.attr(key, target)}
        return this
    },
    update_prop(dom, key, target){
        if (dom.prop(key) != target){dom.prop(key, target)}
        return this
    },
    update_html(dom, target){
        if (dom.html() != target){dom.html(target)}
        return this
    },
    ux_value(dom, to){
        if (R.isNil(to)){return dom.attr("ux-value")}
        dom.attr("ux-value", to)
        return this
    },
    // 以移动端为准自适应页面宽度
    // 返回"pc"     为电脑端，调整body宽度为指定的比率
    // 无返回       为移动端，不作调整
    adaption_mobile_width(pc_body_width_rate){
        const dom_doc = $(document)
        const dom_body = $(document.body)
        // 移动端的初始页面宽度必定<高度
        if (dom_doc.width()*1.3 > dom_doc.height()){
            const width = `${Math.round(dom_doc.height()*pc_body_width_rate)}px`
            dom_body.css("min-width", width).css("max-width", width)
            return "pc"
        }
        return this
    },
}

function Item(html_element_or_selector, events){
    const dom = $(html_element_or_selector)
    events = (R.is(Object, events) ? events : {})
    const me = {}

    me.class_add = (name)=>{
        dom.addClass(name)
        return me
    }
    me.class_del = (name)=>{
        dom.removeClass(name)
        return me
    }

    me.attr = (name, to)=>{
        if (R.isNil(to)){return dom.attr(name)}
        dom.attr(name, to)
        return me
    }
    me.attr_del = (name)=>{
        dom.removeAttr(name)
        return me
    }
    me.ux_value = R.partial(me.attr, ["ux-value"])

    me.prop = (name, to)=>{
        if (R.isNil(to)){return dom.prop(name)}
        dom.prop(name, to)
        return me
    }

    me.visible = (yes)=>{
        (yes ? dom.show() : dom.hide())
        return me
    }

    const _event_maps = {
        on_click: (f)=>{dom.bind("click", ()=>{f(me)})},
        on_mouse_enter: (f)=>{dom.bind("mouseenter", ()=>{f(me)})},
        on_mouse_leave: (f)=>{dom.bind("mouseleave", ()=>{f(me)})},
    }
    for (let e in events){
        const match = _event_maps[e]
        if (match != undefined){
            match(events[e])
        }
    }
    return me
}

function events_pre(events, needs){
    if (! R.is(Object, events)){events = {}}
    for (let name of needs){
        if (! events.hasOwnProperty(name)){
            events[name] = R.always(undefined)
        }
    }
    return events
}
// 单选框
function Ratio(dom, events){
    events = events_pre(events, ["on_select"])
    const me = {}
    dom.bind("click", function(){
        events.on_select(Tools.ux_value($(this)))
    })
    me.select = (f_hit)=>{
        dom.each(function(){
            const val = Tools.ux_value($(this))
            Tools.update_prop($(this), "checked", f_hit(val))
        })
        return me
    }
    me.select_with_eq = (a)=>(me.select((b)=>(R.equals(a, b))))
    return me
}
// 多选框
function Select(dom, events){
    events = events_pre(events, ["on_select", "on_unselect"])
    const me = {}
    dom.on("click", function(){
        const val = Tools.ux_value($(this))
        if ($(this).prop("checked")){
            events.on_select(val)
        }else{
            events.on_unselect(val)
        }
    })
    me.select = (f_hit)=>{
        dom.each(function(){
            const val = Tools.ux_value($(this))
            Tools.update_prop($(this), "checked", f_hit(val))
        })
        return me
    }
    me.foreach = (f)=>{
        dom.each(function(){
            f(Tools.ux_value($(this)), $(this).prop("checked"))
        })
        return me
    }
    return me
}
// 下拉框
function Option(dom, events){
    events = events_pre(events, ["on_select"])
    const dom_options = dom.children("option")
    const obj = dom.get(0)
    const me = {}
    dom.bind("change", function(){
        const i = obj.selectedIndex
        const val = obj.options[i].value
        events.on_select(val)
    })
    me.select = (f_hit)=>{
        for (let i = 0; i < obj.length; i += 1) {
            const val = obj.options[i].value
            if (f_hit(val)) {
                obj.selectedIndex = i
            }
        }
        return me
    }
    me.select_with_eq = (a)=>(me.select((b)=>(R.equals(a, b))))
    me.foreach = (f)=>{
        function session(_this){
            return {
                disable(yes){
                    if (yes){
                        _this.attr("disabled", "disabled")
                    }else{
                        _this.removeAttr("disabled")
                    }
                },
                show(yes){
                    (yes ? _this.show() : _this.hide())
                },
                set_value(value){
                    Tools.ux_value(_this, value)
                    _this.attr("value", value)
                },
            }
        }
        dom_options.each(function(){
            f(Tools.ux_value($(this)), session($(this)))
        })
        return me
    }
    me.foreach((val, session)=>{
        session.set_value(val)
    })
    return me
}
// Tab栏
function Tabs(selector, events){
    events = events_pre(events, [
        "on_select",
        "on_unselect",
        "on_mouse_enter",
        "on_mouse_leave",
    ])
    let last_sel = NaN
    function _select(item){
        if (last_sel != item){
            if (R.is(Object, last_sel)){
                events.on_unselect(last_sel)
            }
            last_sel = item
        }
        events.on_select(item)
    }
    const dom = $(selector)
    const items = dom.children("ux-tab").map(function(){
        return Item(this, {
            on_frame: events.on_frame,
            on_click: _select,
            on_mouse_enter: events.on_mouse_enter,
            on_mouse_leave: events.on_mouse_leave,
        })
    }).get()
    const me = {}
    me.select = (item)=>{
        _select(item)
        return me
    }
    me.select_with_val = (val)=>{
        const item = items.filter((i)=>(i.ux_value() == val))[0]
        if (item != undefined){_select(item)}
        return me
    }
    return me
}

// Canvas 封装
// 使用参考(假设页面有个id为canvas的画布)
// 首先获取接口：const d2d = for_canvas(document.getElementById("canvas"))
// 然后设置d2d.on_before或d2d.on_after = (draw)=>{
//      // 在(10, 10)处绘制一条长度90的横线
//      draw.path_begin().hline(10, 100, 10).path_outline()
//  }
// 最后开始绘制：d2d.start(render)
// 此后会在同步刷新的情况下绘制每帧
// 注意：必须使用d2d.resize(css宽, css高)来初始化/更新canvas的大小
// d2d.resize会调整canvas来适配不同像素比的设备(不然会不清晰)
function for_canvas(dom_canvas){
    const _main = dom_canvas.getContext("2d")
    if (R.isNil(_main)){return undefined}
    const _buffer = _main
    const me = {}

    function fuck_canvas_linewidth(w){
        return ((w % 2) == 0) ? 0 : 0.5
    }
    function frame_things(){}
    me.frame = ()=>{
        if (me.width() > 0 && me.height() > 0){
            me.on_before(me)
            frame_things()
            me.on_after(me)
        }
    }
    me.width = ()=>dom_canvas.width
    me.height = ()=>dom_canvas.height
    // Fuck mobile pixel And Fuck mix(css-size/canvas-size) here is very fuck fuck
    me.resize = (w, h)=>{
        if ((me.width() != w) || (me.height() != h)){
            dom_canvas.style.width = `${w}px`
            dom_canvas.style.height = `${h}px`
            const dpr = window.devicePixelRatio
            const to_w = dpr * w
            const to_h = dpr * h
            dom_canvas.width = to_w
            dom_canvas.height = to_h
            _main.scale(dpr, dpr)
        }
    },
    me.clear = (x, y, w, h)=>{
        _buffer.clearRect(x, y, w, h)
        return me
    }
    me.clear_all = ()=>{
        return me.clear(0, 0, me.width(), me.height())
    }
    me.path_begin = ()=>{
        _buffer.beginPath()
        return me
    }
    me.path_outline = ()=>{
        _buffer.stroke()
        return me
    }
    me.path_fill = ()=>{
        _buffer.fill()
        return me
    }
    me.hline = (x1, x2, y)=>{
        const fuck = fuck_canvas_linewidth(_buffer.lineWidth)
        _buffer.moveTo(x1, y+fuck)
        _buffer.lineTo(x2, y+fuck)
        return me
    }
    me.vline = (y1, y2, x)=>{
        const fuck = fuck_canvas_linewidth(_buffer.lineWidth)
        _buffer.moveTo(x+fuck, y1)
        _buffer.lineTo(x+fuck, y2)
        return me
    }
    me.line = (x1, y1, x2, y2)=>{
        _buffer.moveTo(x1, y1)
        _buffer.lineTo(x2, y2)
        return me
    }
    me.rect = (x, y, w, h)=>{
        _buffer.rect(x, y, w, h)
        return me
    }
    me.circular = (x, y, size)=>{
        _buffer.arc(x, y, size, 0, 2*Math.PI)
        return me
    }
    me.text = (val, x, y)=>{
        _buffer.fillText(val, x, y)
        return me
    },
    me.set_linedash = (segments)=>{
        _buffer.setLineDash(segments)
        return me
    }
    me.set_outline_width = (w)=>{
        _buffer.lineWidth = w
        return me
    }
    me.set_outline_color = (style)=>{
        _buffer.strokeStyle = style
        return me
    }
    me.set_fill_color = (style)=>{
        _buffer.fillStyle = style
        return me
    }
    me.set_font = (px, family, ex="")=>{
        _buffer.font = `${ex} ${px}px ${family}`
        return me
    }
    me.set_text_align = (v)=>{
        _buffer.textAlign = v
        return me
    }
    me.set_text_baseline = (v)=>{
        _buffer.textBaseline = v
        return me
    }
    me.set_opacity = (v)=>{
        _buffer.globalAlpha = Math.max(Math.min(v, 1.0), 0.0)
        return me
    }
    me.push = ()=>{
        _buffer.save()
        return me
    }
    me.pop = ()=>{
        _buffer.restore()
        return me
    }
    me.on_before = R.always(true)
    me.on_after = R.always(true)
    return me
}

export default {
    FrameSystem,
    ...Tools,
    Item,
    Ratio, Select, Option, Tabs,
    for_canvas,
}
